﻿#include "timer_queue.hh"
#include "time_util.hh"
#include "logger/writer.hh"

namespace kratos { namespace corelib {

Timer::Timer(std::time_t timeout, std::time_t trigger, std::uint32_t timerID, bool period)
    : _timeout(timeout), _trigger(trigger), _timerID(timerID), _period(period) {}

Timer::~Timer() {
    _timeout = 0;
    _trigger = 0;
    _timerID = INVALID_TIMER_ID;
    _period = false;
}

std::time_t Timer::getTrigger() const {
    return _trigger;
}

void Timer::reset(std::time_t current) {
    _trigger = current + _timeout;
}

TimerID Timer::getID() const {
    return _timerID;
}

bool Timer::isPeriod() const {
    return _period;
}

TimerQueue::TimerQueue()
    : run_(false), timerIDStart_(1) {}

TimerQueue::~TimerQueue() {
    stop();
}

TimerID TimerQueue::startTimer(std::time_t timeout, std::time_t current) {
    TIMER_QUEUE_PARAM_FAIL_RETURN(!timeout, INVALID_TIMER_ID);
    if (!current) {
        current = TimeUtil::getMillionSecondsTimestampOS();
    }
    auto eventPtr = eventPool_.createObject();
    eventPtr->id = static_cast<std::uint8_t>(TimerEvent::EVENT_START);
    eventPtr->timerID = getNewTimerID();
    eventPtr->timeout = timeout;
    eventPtr->deadline = timeout + current;
    if (!eventInput_.try_enqueue(eventPtr)) {
        return INVALID_TIMER_ID;
    }
    return eventPtr->timerID;
}

TimerID TimerQueue::startTimerOnce(std::time_t timeout, std::time_t current) {
    TIMER_QUEUE_PARAM_FAIL_RETURN(!timeout, INVALID_TIMER_ID);
    if (!current) {
        current = TimeUtil::getMillionSecondsTimestampOS();
    }
    auto eventPtr = eventPool_.createObject();
    eventPtr->id = static_cast<std::uint8_t>(TimerEvent::EVENT_DEADLINE);
    eventPtr->timeout = timeout;
    eventPtr->timerID = getNewTimerID();
    eventPtr->deadline = timeout + current;
    if (!eventInput_.try_enqueue(eventPtr)) {
        return INVALID_TIMER_ID;
    }
    return eventPtr->timerID;
}

TimerID TimerQueue::checkExpired() {
    ExpireEvent event;
    // pump expired timer ID from event queue
    if (!waitOutput_.try_dequeue(event)) {
        return INVALID_TIMER_ID;
    }
    return event.timerID;
}

TimerID TimerQueue::checkExpired(bool& isPeriod) {
    ExpireEvent event;
    // pump expired timer ID from event queue
    if (!waitOutput_.try_dequeue(event)) {
        return INVALID_TIMER_ID;
    }
    isPeriod = event.period;
    return event.timerID;
}

void TimerQueue::processInputEvent(std::time_t) {
    Event* eventPtr = nullptr;
    while (eventInput_.try_dequeue(eventPtr)) {
        switch (static_cast<TimerEvent>(eventPtr->id)) {
        case TimerEvent::EVENT_START: {
            // event to start periodiical timer
            auto timer = timerPool_.createObject(eventPtr->timeout, eventPtr->deadline, eventPtr->timerID, true);
            priorQueue_.push(timer);
            timerIDSet_.insert(eventPtr->timerID);
            break;
        }
        case TimerEvent::EVENT_DEADLINE: {
            // event to start deadline timer
            auto timer = timerPool_.createObject(eventPtr->timeout, eventPtr->deadline, eventPtr->timerID, false);
            priorQueue_.push(timer);
            timerIDSet_.insert(eventPtr->timerID);
            break;
        }
        case TimerEvent::EVENT_CANCEL:
            // event to timer cancellation
            timerIDSet_.erase(eventPtr->timerID);
            break;
        default:
            break;
        }
        // whatever, dispose the event
        eventPool_.disposeObject(eventPtr);
    }
}

void TimerQueue::processTimer(std::time_t now) {
    if (priorQueue_.empty()) {
        return;
    }
    auto timer = priorQueue_.top();
    while (timer) {
        if (timer->getTrigger() > now) {
            return;
        }
        priorQueue_.pop();
        if (timerIDSet_.find(timer->getID()) == timerIDSet_.end()) {
            // The timer was cancelled
            timerPool_.disposeObject(timer);
            timer = nullptr;
            if (!priorQueue_.empty()) {
                // next one
                timer = priorQueue_.top();
                continue;
            } else {
                return;
            }
        }
        ExpireEvent event = {timer->getID(), timer->isPeriod()};
        if (!waitOutput_.try_enqueue(event)) {
            logdebug << "Timer waitOutput_.try_enqueue(timer->getID()) failed" << logend;
        }
        if (timer->isPeriod()) {
            // reset trigger timestamp for periodical timer
            timer->reset(now);
            priorQueue_.push(timer);
        } else {
            // dispose it for deadline timer
            timerIDSet_.erase(timer->getID());
            timerPool_.disposeObject(timer);
        }
        if (!priorQueue_.empty()) {
            timer = priorQueue_.top();
        } else {
            // no more, break for loop
            break;
        }
    }
}

TimerID TimerQueue::getNewTimerID() {
    return timerIDStart_++;
}

bool TimerQueue::start() {
    if (run_) {
        return false;
    }
    run_ = true;
    // setup worker thread
    routine_ = std::thread([&] {
        std::chrono::milliseconds duration(10);
        while (run_) {
            std::this_thread::sleep_for(duration);
            std::time_t current = TimeUtil::getMillionSecondsTimestampOS();
            processInputEvent(current);
            processTimer(current);
        }
        // cleanup, drain the queue and dispose!
        Event* eventPtr = nullptr;
        while (eventInput_.try_dequeue(eventPtr)) {
            eventPool_.disposeObject(eventPtr);
        }
        if (!priorQueue_.empty()) {
            auto timer = priorQueue_.top();
            while (timer) {
                priorQueue_.pop();
                timerPool_.disposeObject(timer);
                if (!priorQueue_.empty()) {
                    timer = priorQueue_.top();
                } else {
                    timer = nullptr;
                }
            }
        }
    });
    return true;
}

bool TimerQueue::stop() {
    if (!run_) {
        return false;
    }
    run_ = false;
    // waiting for worker thread terminating
    routine_.join();
    return true;
}

bool TimerQueue::cancel(TimerID id) {
    TIMER_QUEUE_PARAM_FAIL_RETURN_FALSE(id == INVALID_TIMER_ID);
    auto eventPtr = eventPool_.createObject();
    eventPtr->id = static_cast<std::uint8_t>(TimerEvent::EVENT_CANCEL);
    eventPtr->timerID = id;
    if (!eventInput_.try_enqueue(eventPtr)) {
        TIMER_QUEUE_LOG_ERROR("Timer event queue enqueue failed, timer ID:", id);
        return false;
    }
    return true;
}

bool TimerQueue::isStarted() {
    return run_;
}

}}
